<template>
  <div class="ai-chat-warp">
    <PageTopCover
      :moduleTitle="'ai工具'"
      :imgIcon="'ai.svg'"
      :textIcon="'🤖'"
      :coverImg="getOnlineImgUrl('/imgs/bg/185.png')"
      :isMobile="isMobi"
      :mudulDesc="''"
      :isArticle="false"></PageTopCover>
  </div>
  <div class="ai-chat-content-warp" :class="{ 'm-ai-chat-content-warp': isMobi }">
    <div class="padding">
      <div class="ai-chat-view">
        <ul class="ai-chat-list" ref="aiChatListRef">
          <li class="ai-chat-item">
            <!-- 头像 -->
            <div class="ai-chat-avatar">
              <img class="avater-img" src="@/assets/images/icon/icon-ai.svg" alt="" />
            </div>
            <!-- 聊天内容 -->
            <div class="ai-chat-content-box init-box">
              <div class="ai-chat-title">AI工具 - 星火大模型</div>
              <div class="ai-chat-text" style="color: red">
                特此声明：此AI基于星火大模型，仅供学习使用，不用于任何商业用途，不提供医疗、法律等专业建议。
                星火大模型提供的所有内容均由人工智能模型答复，可能出现错误或遗漏，本服务对其答复内容的准确性、完整性和功能性不作任何保证，并且其输出的内容均不代表本服务提供者的态度或观点。特别提示，您在使用本服务咨询医疗、法律等相关专业问题时，本服务仅提供建议，不代表医疗诊断或法律服务等专业意见。若您需要医疗诊断、法律服务等专业意见，必须咨询医生和相关法律专业人士，并在专业人士的指导下作出决策。总之，本服务提供的内容，应仅供您参考，不构成任何建议或承诺，我们不承担您因使用本服务给您或任何第三方产生的任何损害或责任。
              </div>
            </div>
          </li>
          <li class="ai-chat-item" :class="item.role + '-item'" v-for="(item, index) of chatList">
            <!-- 头像 -->
            <div class="ai-chat-avatar">
              <img class="avater-img" src="@/assets/images/icon/me.png" alt="" v-if="item.role === 'user'" />
              <img class="avater-img" src="@/assets/images/icon/icon-ai.svg" alt="" v-if="item.role === 'assistant'" />
            </div>
            <!-- 聊天内容 -->
            <div class="ai-chat-content-box" :class="item.role + '-box'" v-if="item.role === 'user'">
              {{ item.content }}
            </div>
            <div class="ai-chat-content-box" :class="item.role + '-box'" v-if="item.role === 'assistant'">
              AI回复内容
              <!-- 支持md 预览 -->
              <v-md-preview :text="item.content" @copy-code-success="handleCopyCodeSuccess"></v-md-preview>
              <!-- 加载图标 -->
              <div class="loading-icon-box" v-if="loadingIndex === index">
                <!-- <el-icon>
                  <Loading />
                </el-icon> -->
                <img style="width: 40px; height: 40px; margin-left: 5px" src="@/assets/images/common/loading.svg" />
              </div>
              <div class="ai-chat-operate">
                <!--重新回答  -->
                <span class="re-reply-btn" @click="reReply(index)" :class="{ disabled: sendBtnDisabled }">重新回答</span>
                <div class="operate-icon-box" :class="{ disabled: sendBtnDisabled }">
                  <!-- 聊天内容复制 -->
                  <img style="width: 20px; height: 20px" src="@/assets/images/icon/copy.svg" @click="copyRecord(item, index)" />
                  <!-- 删除聊天内容 -->
                  <img style="width: 20px; height: 20px; margin-left: 10px" src="@/assets/images/icon/icon-close.svg" @click="deleteRecord(index)" />
                </div>
              </div>
            </div>
          </li>
        </ul>
        <!-- 文本发送区域 -->
        <div class="ai-chat-form-wrapper">
          <div class="ai-chat-form-box">
            <!-- <textarea
              style="width: 100%"
              v-model="problemText"
              :rows="4"
              placeholder="在此输入您想要了解的内容..."
              @keydown.enter.exact.prevent="sendQuestion"
              @keydown.enter.shift.exact.prevent="problemText += '\n'"></textarea> -->
            <div class="input-out-warp">
              <div class="input-warp" :class="{ 'no-input-warp': !problemText }">
                <textarea
                  style="width: 100%"
                  type="textarea"
                  :class="{ 'have-text': problemText }"
                  @keydown.enter.exact.prevent="sendQuestion"
                  @keydown.enter.shift.exact.prevent="problemText += '\n'"
                  class="input-text"
                  :rows="4"
                  placeholder=""
                  v-model="problemText" />
                <label class="input-label">{{ problemText ? '你是我一生只会遇见一次的惊喜 ...' : '在此输入您想要了解的内容...' }}</label>
              </div>
              <div class="chat-form-footer pointer" @click="sendQuestion">
                <div class="btns">
                  <span class="content-tips">{{ problemText.length }} / {{ maxCharCount }}</span>
                  <span>
                    <div style="display: inline-block; margin-left: 10px" class="pointer" :disabled="sendBtnDisabled">发送</div>
                  </span>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { sparkConfig } from '@/config/config.ts';
import useResize from '@/hooks/useResize';
import { ChatItem, getWebsocketUrl, WSReqParams, WSResParams, wsSendMsgFormat } from '@/utils/AiChatWebSocket';
import { CustomMessage, getOnlineImgUrl } from '@/utils/common';
import { copyToClipboard } from '@/utils/commonUtil.ts';
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';

const { isMobi } = useResize();
//定义websocket 实例
let sparkWS: WebSocket;
//会话列表
let chatList = ref<ChatItem[]>([]);
//加载当v前回答的index
let loadingIndex = ref<number | null | undefined>();
//提问问题内容
let problemText = ref<string>('');
//websocket 响应数据的状态
let wsMsgReceiveStatus = ref<'receiveIng' | 'receiveFinished'>();
//提问的最大字数
const maxCharCount = ref<number>(300);
//发送按钮的禁用状态
const sendBtnDisabled = ref(false);
//定义会话列表的观察对象（观察子元素的变化，当会话有变化时，自动滚动到变化的位置，用于提高用户体验度）
let chatListObserver: MutationObserver;
//聊天列表引用对象
const aiChatListRef = ref();

onMounted(() => {
  //调用监听目标元素的高度变化函数
  createMutationObserver(aiChatListRef.value);
});
/**
 * 聊天列表变化的观察对象：用于监听目标元素的高度变化
 * @param targetElement  要监听的目标元素
 */
const createMutationObserver = (targetElement: Element) => {
  //创建MutationObserver 实例，用于检测DOM变化
  chatListObserver = new MutationObserver((mutationsList, observer) => {
    //当子元素发生变化时，获取元素的滚动区域高度
    const scrollHeight = targetElement.scrollHeight;
    //滚动的处理
    scrollHandle(scrollHeight + 130);
  });
  //启动监测器并配置所需的观察选项
  chatListObserver.observe(targetElement, { childList: true, subtree: true });
};
/**
 * 滚动定位处理
 * @param val
 */
const scrollHandle = (val: number) => {
  aiChatListRef.value?.scrollTo({
    //scrollTo:把内容滚动到指定的坐标
    top: val,
    behavior: 'smooth'
  });
};
//发送问题的函数
const sendQuestion = () => {
  if (sendBtnDisabled.value) {
    //阻止内容发送
    return;
  }
  if (problemText.value?.trim()?.length <= 0) {
    //输入问题文字是空字符串的提示
    CustomMessage({ type: 'warning', message: '请输入您想要了解的内容...' });
    return;
  }
  //不在接受消息的时候才可以发送问题
  if (wsMsgReceiveStatus.value !== 'receiveIng') {
    chatList.value.push({
      role: 'user',
      content: problemText.value
    });
    sendBtnDisabled.value = true;
    //调用连接星火认知大模型并发送问题的函数
    askSpark();
  }
};
//连接星火认知大模型并发送问题
const askSpark = () => {
  //1. 生成鉴权URL
  let wsUrl = getWebsocketUrl(sparkConfig);
  //2.判断浏览器是否websocket
  if ('WebSocket' in window) {
    //创建websocket 实例
    sparkWS = new WebSocket(wsUrl as string);
  } else {
    CustomMessage({ type: 'error', message: '当前浏览器不支持websocket' });
    return;
  }
  //3. 建立websocket连接
  //3.1 打开连接
  sparkWS.onopen = () => {
    //发送数据
    const sendData: WSReqParams = wsSendMsgFormat(sparkConfig, chatList.value);
    sparkWS.send(JSON.stringify(sendData));

    chatList.value.push({
      role: 'assistant',
      content: ''
    });
    loadingIndex.value = chatList.value.length - 1;
  };
  //3.2 消息监听
  sparkWS.onmessage = (res: MessageEvent) => {
    //响应消息
    let resObj: WSResParams = JSON.parse(res.data);
    if (resObj.header.code !== 0) {
      CustomMessage({ type: 'error', message: '提问失败' });
      console.error(`提问失败：${resObj.header.code} - ${resObj.header.message}`);
      //连接关闭
      sparkWS.close();
    } else {
      //处理响应回来的数据
      wsMsgReceiveHandle(resObj);
    }
  };
  //3.3 连接异常
  sparkWS.onerror = e => {
    CustomMessage({ type: 'error', message: 'WebSocket 连接失败' + e });
    console.error(`WebSocket 连接失败,连接的URL${wsUrl}`);
  };
  //3.4 连接关闭
  sparkWS.onclose = () => {};
};
/**
 * 处理websocket 返回的数据
 * @param res WSResParams
 */
const wsMsgReceiveHandle = (res: WSResParams) => {
  let dataArray = res?.payload?.choices?.text || [];
  for (let i = 0; i < dataArray.length; i++) {
    chatList.value[chatList.value.length - 1].content += dataArray[i].content;
  }
  //开始接受消息
  if (res.payload.choices.status === 0) {
    problemText.value = '';
    wsMsgReceiveStatus.value = 'receiveIng';
  }
  //继续接受消息
  if (res.payload.choices.status === 1) {
    wsMsgReceiveStatus.value = 'receiveIng';
  }
  //接受消息完毕
  if (res.payload.choices.status === 2) {
    wsMsgReceiveStatus.value = 'receiveFinished';
    loadingIndex.value = null;
    sendBtnDisabled.value = false;
    sparkWS.close();
  }
};
/**
 * 复制会话内容到剪贴板
 * @param item
 * @param index
 */
const copyRecord = (item: { content: any }, index: any) => {
  const content = item.content;
  copyToClipboard({
    content,
    success() {
      CustomMessage({ type: 'success', message: '复制成功' });
    },
    error() {
      CustomMessage({ type: 'error', message: '复制失败' });
    }
  });
};
/**
 * AI 回答内容中的代码块复制
 */
const handleCopyCodeSuccess = () => {
  CustomMessage({ type: 'success', message: '复制成功' });
};
/**
 * 删除聊天记录
 * @param index
 */
const deleteRecord = (index: number) => {
  if (!sendBtnDisabled.value) {
    chatList.value.splice(index, 1);
  }
};
/**
 * 重新回答
 */
const reReply = (index: number) => {
  if (wsMsgReceiveStatus.value !== 'receiveIng') {
    //如果是最后一条重新回答，直接删除最后一条回答记录直接作答
    if (chatList.value.length - 1 === index) {
      deleteRecord(index);
      sendBtnDisabled.value = true;
      askSpark();
    } else {
      //如果不是最后一条重新回答，则后面重新添加问题继续进行询问
      //有可能上一条回答内容被直接删除，所以需要循环往前找最近的一条问题记录进行作答
      let i = index - 1;
      while (i >= 0) {
        //角色为用户，有问题内容
        if (chatList.value[i].role === 'user' && chatList.value[i].content) {
          chatList.value.push({
            role: 'user',
            content: chatList.value[index - 1].content
          });
          sendBtnDisabled.value = true;
          askSpark();
          break;
        }
        i--;
      }
    }
  }
};
/**
 * 监听提问问题内容的长度
 */
const problemTextWatcher = watch(
  () => problemText.value,
  () => {
    //限制最大字数
    if (problemText.value.length > maxCharCount.value) {
      problemText.value = problemText.value.slice(0, maxCharCount.value);
    }
  }
);
/**
 * 组件销毁之前
 */
onBeforeUnmount(() => {
  //停止监听提问问题内容的长度
  problemTextWatcher();
  //停止会话列表内容的监听
  chatListObserver.disconnect();
});
</script>

<style lang="scss" scoped>
.ai-chat-content-warp {
  min-height: calc(100vh - 400px);
  max-height: calc(100vh + 400px);
  .padding {
    min-height: calc(100vh - 400px);
    max-height: calc(100vh + 400px);
    height: 100%;
    padding: 20px 0;
  }
}
.m-ai-chat-content-warp {
  min-height: calc(100vh - 300px);
  max-height: calc(100vh + 300px);
  .padding {
    min-height: calc(100vh - 300px);
  }
  .ai-chat-view {
    width: var(--content-max-width-m) !important;
    min-height: calc(100vh - 340px);
    max-height: calc(100vh + 260px);
  }
}
.ai-chat-view {
  min-height: calc(100vh - 440px);
  max-height: calc(100vh + 360px);
  height: 100%;
  max-width: var(--content-max-width);
  margin: 0 auto;
  display: flex;
  background-color: var(--bg-content-color);
  flex-direction: column;
  flex: 1;
  overflow: hidden;
  padding: 15px 15px;
  border-radius: var(--border-radius-1);

  //对话列表
  .ai-chat-list {
    display: flex;
    flex: 1;
    flex-direction: column;
    overflow-y: auto;
    margin-bottom: 20px;
    //消除浏览器滚动条
    &::-webkit-scrollbar {
      display: none;
    }

    //会话项
    .ai-chat-item {
      display: flex;
      align-items: flex-start;
      margin-bottom: 40px;
    }

    .user-item {
      flex-direction: row-reverse;
    }

    //会话头像
    .ai-chat-avatar {
      margin-right: 10px;
    }

    .avater-img {
      width: 30px;
      height: 30px;
      border-radius: 50%;
    }

    //会话盒子
    .ai-chat-content-box {
      padding: 16px 30px;
      background-color: var(--bg-content-color) !important;
      box-shadow: var(--box-shadow);

      //加载图标盒子
      .loading-icon-box {
        .el-icon {
          transform: translate(0, 0);
          animation: rotate 3s linear infinite;
        }

        @keyframes rotate {
          0% {
            transform: translate(0, 0) rotate(0deg);
          }

          100% {
            transform: translate(0, 0) rotate(360deg);
          }
        }
      }

      //会话列表初始化盒子
      &.init-box {
        width: 100%;
        background-image: url('https://ydcqoss.ydcode.cn/static/officialhome/ai-chat-init-bg.png');
        background-repeat: no-repeat;
        background-size: cover;
        border-radius: 10px;

        .ai-chat-title {
          font-size: 1.125rem;
          color: #005fdb;
          margin-bottom: 1rem;
        }

        .ai-chat-text {
          font-size: 0.875rem;
          color: #666666;
          line-height: 1.8;
        }
      }

      //会话列表用户盒子
      &.user-box {
        background-color: var(--bg-content-color);
        border-radius: var(--border-radius-1);
        line-height: 2;
        margin-right: 10px;
        box-shadow: var(--box-shadow);
        padding: 8px 20px;
      }

      //会话列表ai回复盒子
      &.assistant-box {
        border-radius: 10px;
        background: #eff7ff;
        width: 100%;
      }
    }

    //会话操作
    .ai-chat-operate {
      display: flex;
      justify-content: space-between;
      align-items: center;
      cursor: pointer;

      //重复回答
      .re-reply-btn {
        font-size: 14px;
        color: #005fdb;

        &.disabled {
          color: #ccc;
        }
      }

      //操作图标
      .operate-icon-box {
        display: flex;
        align-items: center;

        .el-icon {
          color: #005fdb;
          font-size: 16px;
          margin-left: 16px;
          cursor: pointer;
        }

        &.disabled .el-icon {
          color: #ccc;
        }
      }
    }
  }
}

.input-out-warp {
  border-radius: var(--border-radius-2);
  position: relative;
  .input-warp {
    background-color: var(--bg-content-color);
    position: relative;
    padding: 10px 20px;
    border: 1px solid var(--text-color);
    background-image: url('@/assets/images/common/comment-bg.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: right;
    resize: vertical;
    border-radius: var(--border-radius-2);
    &:focus-within {
      border-color: var(--theme-color);
    }
    .input-text {
      width: calc(100% - 0px) !important;
      min-height: 60px;
      font-size: 14px;
      border: none;
      background-color: transparent;
      outline: none;
      color: var(--text-color);
      &:focus {
        border-color: var(--theme-color);
        + .input-label {
          top: -6px;
          font-size: 12px;
          line-height: 14px;
          background-color: #999;
          border-radius: 2px;
        }
      }
    }
    .have-text {
      + .input-label {
        top: -6px !important;
        font-size: 12px !important;
        line-height: 14px !important;
        background-color: #999 !important;
        border-radius: 2px !important;
      }
    }

    .input-label {
      position: absolute;
      left: 16px;
      top: 14px;
      padding: 0 6px;
      transform-origin: 0 0;
      pointer-events: none;
      transition: all 0.25s;
      color: var(--text-color);
    }
  }
  .chat-form-footer {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    padding: 10px;
  }
}
</style>
